import { CK_APP_PROGRESS_CURRENT_ROLE, CK_TOKEN } from '@/constants/cache-key';
import { ServerSideException } from '@/constants/server-side-exception';
import * as toast from '@/utils/toast';
import { cache } from '@utils/cache';
import {
  api,
  type ExtraConfig,
  type HttpMethod,
  type IHttp,
  type IHttpResponse,
} from '@utils/http';
import { has, isEmpty, isNil, isString } from 'lodash-es';
import { reset_pinia_stores } from './pinia';

class Exception extends Error {
  static create(data: Partial<{ code: number; message: string }>) {
    const { message = '', code = 500 } = data;
    return new Exception(message, code);
  }
  constructor(
    public readonly message: string = '',
    public readonly code: number = 500
  ) {
    super(message);
  }

  get data() {
    return {
      code: this.code,
      message: this.message,
    };
  }
}

function is_ok(response: IHttpResponse<any>) {
  if (has(response, 'code')) {
    return response.code === 0;
  } else {
    return true;
  }
}

function useApi(): IHttp {
  class HttpProxy implements IHttp {
    async call<T = unknown>(
      method: HttpMethod,
      url: string,
      parameters: Record<string, unknown> = {},
      extraConfig: ExtraConfig = {}
    ): Promise<IHttpResponse<T>> {
      extraConfig.enableBrowserCache = true;
      const global_exception_handlers = [
        {
          test: (apiResponse: any) => {
            return new Set([ServerSideException.TOKEN_EMPTY, ServerSideException.TOKEN_ERROR]).has(
              apiResponse.code
            );
          },
          handle: async (error: any) => {
            toast.error(error.msg || 'Oops! Something is wrong!');
            setTimeout(async () => {
              await cache.clear();
              reset_pinia_stores();
              window.location.href = '/sign-in';
            }, 2500);
          },
        },
      ];
      const token = await cache.getItem(CK_TOKEN);
      const fk_business_id = await cache.getItem(CK_APP_PROGRESS_CURRENT_ROLE);
      const http = api(API_BASE_URL, isString(token) && !isEmpty(token) ? { token } : {});
      http.afterReturning(async (cthulhu) => {
        console.log('http.afterReturning', is_ok(cthulhu));
        if (!is_ok(cthulhu)) {
          for (const i of global_exception_handlers) {
            if (i.test(cthulhu)) {
              await i.handle(cthulhu);
            }
          }
          return false;
        }
      });
      const data = await http.call<T>(
        method,
        url,
        isNil(fk_business_id)
          ? parameters
          : {
              fk_business_id,
              ...parameters,
            },
        extraConfig
      );
      return data;
    }

    async get<T = unknown>(
      url: string,
      parameters: Record<string, unknown> = {},
      extraConfig: ExtraConfig = {}
    ): Promise<IHttpResponse<T>> {
      return this.call<T>('GET', url, parameters, extraConfig);
    }

    async delete<T = unknown>(
      url: string,
      parameters: Record<string, unknown> = {},
      extraConfig: ExtraConfig = {}
    ): Promise<IHttpResponse<T>> {
      return this.call<T>('DELETE', url, parameters, extraConfig);
    }

    async patch<T = unknown>(
      url: string,
      parameters: Record<string, unknown> = {},
      extraConfig: ExtraConfig = {}
    ): Promise<IHttpResponse<T>> {
      return this.call<T>('PATCH', url, parameters, extraConfig);
    }

    async post<T = unknown>(
      url: string,
      parameters: Record<string, unknown> = {},
      extraConfig: ExtraConfig = {}
    ): Promise<IHttpResponse<T>> {
      return this.call<T>('POST', url, parameters, extraConfig);
    }

    async put<T = unknown>(
      url: string,
      parameters: Record<string, unknown> = {},
      extraConfig: ExtraConfig = {}
    ): Promise<IHttpResponse<T>> {
      return this.call<T>('PUT', url, parameters, extraConfig);
    }
  }

  const singleton = new HttpProxy();

  return singleton as IHttp;
}

export { Exception, is_ok, useApi };
